package org.cnc.explain.db.transaction.service.impl;

import org.cnc.explain.db.transaction.entity.Account;
import org.cnc.explain.db.transaction.mapper.AccountMapper;
import org.cnc.explain.db.transaction.service.AccountService;
import org.cnc.explain.db.transaction.service.MockService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.io.IOException;
import java.math.BigDecimal;

@Service
public class AccountServiceImpl implements AccountService {

	private final AccountMapper accountMapper;
	private final MockService mockService;
	private final PlatformTransactionManager platformTransactionManager;

	public AccountServiceImpl(AccountMapper accountMapper, MockService mockService, PlatformTransactionManager platformTransactionManager) {
		this.accountMapper = accountMapper;
		this.mockService = mockService;
		this.platformTransactionManager = platformTransactionManager;
	}


	@Override
	public Account queryByAccountId(Integer accountId) {
		return accountMapper.findByAccountId(accountId);
	}

	@Override
	public Integer updateWithoutTransactional(Account from, Account to, BigDecimal amount) {
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		mockService.maybeThrowUncheckedException();
		accountMapper.updateById(to);
		return 100;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Integer updateTransactional(Account from, Account to, BigDecimal amount) {
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		mockService.maybeThrowUncheckedException();
		accountMapper.updateById(to);
		return 100;
	}

	@Override
	@Transactional(rollbackFor = IOException.class)
	public Integer transactionRollbackSuccess(Account from, Account to, BigDecimal amount) throws IOException {
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		mockService.maybeThrowIoException();
		accountMapper.updateById(to);
		return 100;
	}

	@Override
	@Transactional(rollbackFor = ArithmeticException.class)
	public Integer transactionRollbackFailure(Account from, Account to, BigDecimal amount) throws IOException {
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		mockService.maybeThrowIoException();
		accountMapper.updateById(to); 
		return 100;
	}

	@Override
	public Integer programmerTransactionRollback(Account from, Account to, BigDecimal amount) {
		// 非事务阶段
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		accountMapper.updateById(to);


		// 事务管理阶段
		TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
		TransactionStatus ts = platformTransactionManager.getTransaction(transactionDefinition);
		try {
			System.out.println("开始函数式事务...");
			from.setAmount(from.getAmount().add(amount));
			to.setAmount(to.getAmount().subtract(amount));
			int a = 10;
			int b = 0;
			int c = a / b;
			accountMapper.updateById(from);
			accountMapper.updateById(to);
			platformTransactionManager.commit(ts);
		} catch (Exception e) {
			System.out.println("执行事务回滚...");
			platformTransactionManager.rollback(ts);
		}
		return 100;
	}


	@Override
	public Integer programmerTransactionNoneRollback(Account from, Account to, BigDecimal amount) {
		// 非事务阶段
		from.setAmount(from.getAmount().subtract(amount));
		to.setAmount(to.getAmount().add(amount));
		accountMapper.updateById(from);
		accountMapper.updateById(to);

		from.setAmount(from.getAmount().add(amount));
		to.setAmount(to.getAmount().subtract(amount));
		accountMapper.updateById(from);
		accountMapper.updateById(to);

		return 100;
	}

}
